package by.bucha.testproject.imageLoader;

import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import by.bucha.tabnavigationproject.R;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.widget.ImageView;

public class ImageLoader {

	public ImageLoader() {
		mImageViews = Collections
				.synchronizedMap(new WeakHashMap<ImageView, String>());
		mExecutorService = Executors.newFixedThreadPool(MAX_THREADS);
		mException = null;
		mLruCache = new BitmapLruCache<String>(getCacheMemory());
	}

	private BitmapLruCache<String> mLruCache;

	private Map<ImageView, String> mImageViews;

	private ExecutorService mExecutorService;

	private int MAX_THREADS = 3;

	private int mPlaceholder = R.drawable.ic_stub_image;

	private int mPlaceholderNoInternet = R.drawable.ic_no_internet;

	private Exception mException;

	public static final int SUCCESS_RESPONSE = 0;

	public static final int FAILURE_RESPONSE = 1;

	public static final String EXTRA_BITMAP = "downloaded bitmap";

	public static final String EXTRA_EXCEPTION = "occured exception";

	public void bind(final ImageLoaderData data,
			final ImageLoaderCallback callBack) {
		if (data.getImageView() == null) {
			callBack.onError(new NullPointerException("ImageView is null"));
		}
		if (data.getPath() == null) {
			callBack.onError(new NullPointerException("URL is null"));
		}
		Bitmap bitmap = mLruCache.get(data.getPath());
		if (bitmap != null) {
			data.getImageView().setImageBitmap(bitmap);
			return;
		} else {
			mImageViews.put(data.getImageView(), data.getPath());
			data.getImageView().setImageResource(mPlaceholder);
			mExecutorService.execute(new BitmapLoader(data, new ResultReceiver(
					new Handler()) {
				@Override
				protected void onReceiveResult(int resultCode, Bundle resultData) {
					if (resultCode == SUCCESS_RESPONSE) {
						Bitmap bitmap = resultData.getParcelable(EXTRA_BITMAP);
						if (!isImageViewReused(data.getImageView(),
								data.getPath())) {
							mImageViews.remove(data.getImageView());
							data.getImageView().setImageBitmap(bitmap);
						}
						if (mException != null) {
							mException = null;
						}
						callBack.onSucsess();
					} else if (resultCode == FAILURE_RESPONSE) {
						if (!isImageViewReused(data.getImageView(),
								data.getPath())) {
							mImageViews.remove(data.getImageView());
							data.getImageView().setImageResource(
									mPlaceholderNoInternet);
						}
						Exception exception = (Exception) resultData
								.getSerializable(EXTRA_EXCEPTION);
						if (mException == null) {
							mException = exception;
							callBack.onError(exception);
						} else if (!mException.getMessage().equals(
								exception.getMessage())) {
							mException = exception;
							callBack.onError(exception);
						}
					}
				}
			}));
		}
	}

	private class BitmapLoader implements Runnable {

		private ImageLoaderData data;

		private ResultReceiver callback;

		public BitmapLoader(ImageLoaderData data, ResultReceiver callback) {
			this.data = data;
			this.callback = callback;
		}

		@Override
		public void run() {
			if (!isImageViewReused(data.getImageView(), data.getPath())) {
				Bitmap bitmap = mLruCache.get(data.getPath());
				if (bitmap == null) {
					try {
						bitmap = decodeScaledBitmap(data.getPath(),
								data.getImageWidth(), data.getImageHight());
					} catch (Exception e) {
						Bundle exception = new Bundle();
						exception.putSerializable(EXTRA_EXCEPTION, e);
						callback.send(FAILURE_RESPONSE, exception);
						return;
					}
					mLruCache.put(data.getPath(), bitmap);
				}
				Bundle bundleToSend = new Bundle();
				bundleToSend.putParcelable(EXTRA_BITMAP, bitmap);
				callback.send(SUCCESS_RESPONSE, bundleToSend);
				return;
			}
		}
	}

	/** Resize bitmap to size of display */
	/*
	 * @SuppressLint("NewApi") private Bitmap getBitmap(String path) {
	 * 
	 * WindowManager window = (WindowManager) mContext
	 * .getSystemService(Context.WINDOW_SERVICE); Display display =
	 * window.getDefaultDisplay(); int displayWidth; int displayHeight; if
	 * (Build.VERSION.SDK_INT > 12) { Point size = new Point();
	 * display.getSize(size); displayWidth = size.x; displayHeight = size.y; }
	 * else { displayWidth = display.getWidth(); displayHeight =
	 * display.getHeight(); } return decodeScaledBitmap(path, 30, 30); }
	 */

	private Bitmap decodeScaledBitmap(String filePath, int reqWidth,
			int reqHeight) {

		// First decode with inJustDecodeBounds=true to check dimensions
		final BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(filePath, options);

		// Calculate inSampleSize
		options.inSampleSize = calculateInSampleSize(options, reqWidth,
				reqHeight);

		// Decode bitmap with inSampleSize set
		options.inJustDecodeBounds = false;
		return BitmapFactory.decodeFile(filePath, options);
	}

	private int calculateInSampleSize(BitmapFactory.Options options,
			int reqWidth, int reqHeight) {
		// Raw height and width of image
		final int height = options.outHeight;
		final int width = options.outWidth;
		int inSampleSize = 1;

		if (height > reqHeight || width > reqWidth) {

			// Calculate ratios of height and width to requested height and
			// width
			final int heightRatio = Math.round((float) height
					/ (float) reqHeight);
			final int widthRatio = Math.round((float) width / (float) reqWidth);

			// Choose the smallest ratio as inSampleSize value, this will
			// guarantee
			// a final image with both dimensions larger than or equal to the
			// requested height and width.
			inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
		}

		return inSampleSize;
	}

	/**
	 * Get max capacity of LRU cache it gives 1/4 of the maximum available
	 * memory
	 */
	private int getCacheMemory() {
		int maxCachMemory;
		// imageCacheLimit measured in bits 2097152 equals 2 Mb
		int imageCacheLimit = 2097152;
		int size = (int) (Runtime.getRuntime().maxMemory() / 4);
		if (size > imageCacheLimit) {
			maxCachMemory = size;
		} else {
			maxCachMemory = imageCacheLimit;
		}
		return maxCachMemory;
	}

	/** Check if ImageView reused */
	private Boolean isImageViewReused(ImageView imageView, String url) {
		String value = mImageViews.get(imageView);
		if (value == null || !value.equals(url)) {
			return true;
		}
		return false;
	}
}